﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Web.Mvc;
using System.Xml.Linq;
using System.Globalization;

namespace Toolkit.Web.Mvc.ValueProviderFactories
{
    /// <summary>
    /// A factory that will provide a value provider that will handle requests that are encoded as application/json. To use this
    /// you must include the following in your Application_Start method in Global.asax.cs file:
    /// 
    /// ValueProviderFactories.Factories.Add(new CustomXmlValueProviderFactory());
    /// 
    /// Note:
    /// Started from http://www.nogginbox.co.uk/blog/xml-to-asp.net-mvc-action-method but has been modified.
    /// </summary>
    public sealed class XmlValueProviderFactory : ValueProviderFactory
    {
        private static void AddToBackingStore(IDictionary<string, object> backingStore, string prefix, XElement xmlDoc)
        {
            // Check the keys to see if this is an array or an object.
            IList<string> uniqueKeys = new List<string>();
            int totalCount = 0;
            foreach (XElement element in xmlDoc.Elements())
            {
                if (!uniqueKeys.Contains(element.Name.LocalName))
                {
                    uniqueKeys.Add(element.Name.LocalName);
                }
                totalCount++;
            }

            bool isArray;
            if (uniqueKeys.Count == 1)
            {
                isArray = true;
            }
            else if (uniqueKeys.Count == totalCount)
            {
                isArray = false;
            }
            else
            {
                // Not sure how to deal with a XML doc that has some keys the same, but not all.
                // For now don't process this node.
                return;
            }

            // Add the elements to the backing store.
            int elementCount = 0;
            foreach (XElement element in xmlDoc.Elements())
            {
                if (element.HasElements)
                {
                    if (isArray)
                    {
                        // Omit local name for arrays and add index instead.
                        AddToBackingStore(backingStore, string.Format("{0}[{1}]", prefix, elementCount), element);
                    }
                    else
                    {
                        AddToBackingStore(backingStore, MakePropertyKey(prefix, element.Name.LocalName), element);
                    }
                }
                else
                {
                    backingStore.Add(MakePropertyKey(prefix, element.Name.LocalName), element.Value);
                }

                elementCount++;
            }
        }

        private static XDocument GetDeserializedXml(ControllerContext controllerContext)
        {
            if (!controllerContext.HttpContext.Request.ContentType.StartsWith("text/xml", StringComparison.OrdinalIgnoreCase) && !controllerContext.HttpContext.Request.ContentType.StartsWith("application/xml", StringComparison.OrdinalIgnoreCase))
            {
                // Not a xml request.
                return null;
            }

            XDocument xml = null;

            try
            {
                XmlTextReader xmlReader = new XmlTextReader(controllerContext.HttpContext.Request.InputStream);
                xml = XDocument.Load(xmlReader);

                if (xml.FirstNode == null)
                {
                    // No xml data.
                    return null;
                }
            }
            catch (Exception)
            {
                // No xml data.
                return null;
            }

            return xml;
        }

        public override IValueProvider GetValueProvider(ControllerContext controllerContext)
        {
            if (controllerContext == null)
            {
                throw new ArgumentNullException("controllerContext");
            }

            XDocument xmlData = GetDeserializedXml(controllerContext);
            if (xmlData == null)
            {
                return null;
            }

            IDictionary<string, object> backingStore = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
            AddToBackingStore(backingStore, string.Empty, xmlData.Root);
            return new DictionaryValueProvider<object>(backingStore, CultureInfo.CurrentCulture);
        }

        private static string MakeArrayKey(string prefix, int index)
        {
            return prefix + "[" + index.ToString(CultureInfo.InvariantCulture) + "]";
        }

        private static string MakePropertyKey(string prefix, string propertyName)
        {
            return (string.IsNullOrEmpty(prefix)) ? propertyName : prefix + "." + propertyName;
        }
    }
}
